package com.facebook.app.server.facebook;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.jdo.PersistenceManager;

import com.facebook.app.client.ClusteringMethod;
import com.facebook.app.client.FacebookService;
import com.facebook.app.server.clustering.ClusteringStrategy;
import com.facebook.app.server.clustering.CosineSimilarityStrategy;
import com.facebook.app.server.clustering.HierarchicalClusteringWithArrays;
import com.facebook.app.server.clustering.KMeansClustering;
import com.facebook.app.server.clustering.SimpleSimilarityStrategy;
import com.facebook.app.shared.clustering.Clusterable;
import com.facebook.app.shared.facebookData.DataStorage;
import com.facebook.app.shared.facebookData.FacebookFriend;
import com.facebook.app.shared.facebookData.FacebookLike;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class FacebookServiceImpl extends RemoteServiceServlet implements
		FacebookService {

	private static final long serialVersionUID = -3407656711725976695L;

	/**
	 * Deletes all FacebookFriends (and their FacebookLikes) from the datastore.
	 */
	@Override
	public Void deleteDataFromTheStore() {

		System.out.println("Deleting data starts");

		PersistenceManager pm = PersistenceManagerFactorySingleton
				.getPersistenceManagerFactoryInstance().getPersistenceManager();

		try {

			pm.newQuery(FacebookFriend.class).deletePersistentAll();

		} finally {
			pm.close();
		}

		System.out.println("Deleting data ends");
		return null;
	}

	/**
	 * Retrieves all FacebookFriends from the datastore and saves their
	 * FacebookLikes in a set. Besides, the characteristicClusterVector of each
	 * friend is calculated.
	 */
	@Override
	public List<Clusterable> prepareClustering(FacebookFriend[] allFriends) {

		PersistenceManager pm = PersistenceManagerFactorySingleton
				.getPersistenceManagerFactoryInstance().getPersistenceManager();

		DataStorage<FacebookLike> likeStorage = new DataStorage<FacebookLike>();
		
		List<FacebookFriend> allSavedFriends;

		try {
			@SuppressWarnings("unchecked")
			// This cast is totally safe.
			List<FacebookFriend> tmpList = (List<FacebookFriend>) pm
					.newQuery(FacebookFriend.class, Arrays.asList(allFriends))
					.execute();
			
			allSavedFriends = tmpList;

			pm.retrieveAll(allSavedFriends); // likes in defaultFetchGroup ?

		} finally {
			pm.close();
		}

		for (FacebookFriend friend : allSavedFriends) {
			likeStorage.addAllDataToSet(friend.getLikes());
		}

		List<Clusterable> clusterElements = new LinkedList<Clusterable>();

		// FacebookFriends without likes are "sorted out" before clustering
		// starts.
		for (FacebookFriend friend : allSavedFriends) {
			if (friend.getLikes() != null && friend.getLikes().size() > 0) {
				friend.adaptCharacteristicClusterVectorToLikeCollection(likeStorage
						.getDataSet());
				clusterElements.add(friend);
			}
		}

		// pm.deletePersistentAll(allSavedFriends);

		return clusterElements;
	}

	@Override
	public List<Clusterable> initiateClustering(
			ClusteringMethod clusteringMethod, List<Clusterable> clusterElements) {

		if (clusteringMethod == null) {
			throw new NullPointerException("clusteringMethod has a null value.");
		}

		ClusteringStrategy clusteringStrategy;

		switch (clusteringMethod) {

		case COSINE_HIERARCHICAL_CLUSTERING:
			clusteringStrategy = new HierarchicalClusteringWithArrays(
					new CosineSimilarityStrategy(), clusterElements);
			break;

		case COSINE_K_MEANS:
			clusteringStrategy = new KMeansClustering(
					new CosineSimilarityStrategy(), clusterElements);
			break;

		case SIMPLE_HIERARCHICAL_CLUSTERING:
			clusteringStrategy = new HierarchicalClusteringWithArrays(
					new SimpleSimilarityStrategy(), clusterElements);
			break;

		case SIMPLE_K_MEANS:
			clusteringStrategy = new KMeansClustering(
					new SimpleSimilarityStrategy(), clusterElements);
			break;

		default:
			throw new IllegalArgumentException("Unknown clusteringMethod: "
					+ clusteringMethod);
		}

		return clusteringStrategy.createClusters();
	}

	/**
	 * Imports all FacebookFriends and returns them in an array.
	 */
	@Override
	public FacebookFriend[] importFriends(String accessToken) {

		System.out.println("starts importing friends");

		FacebookImporter fbImporter = new FacebookImporter(accessToken);

		FacebookFriend[] friends = fbImporter.importFriends();
		System.out.println("Number of Friends: " + friends.length);
		return friends;
	}

	/**
	 * Imports the FacebookLikes for a certain number of FacebookFriends.
	 */
	@Override
	public Void importLikeData(FacebookFriend[] friends, String accessToken) {

		System.out.println("New request!");

		FacebookImporter fbImporter = new FacebookImporter(accessToken);

		fbImporter.importAndSaveFacebookData(friends);
		System.out.println("import finished");
		return null;
	}

	@Override
	public Set<FacebookLike> importLikesForFriends(
			List<FacebookFriend> friends, String accessToken) {

		// TODO use data from the store instead
		// pm.makeTransientAll(commonLikes)

		FacebookImporter fbImporter = new FacebookImporter(accessToken);

		for (FacebookFriend friend : friends) {
			friend.setLikesWithArray(fbImporter.importLikesForFriend(friend));
		}

		Set<FacebookLike> commonLikes = new HashSet<FacebookLike>();

		boolean firstFriend = true;

		for (FacebookFriend friend : friends) {
			if (firstFriend) {
				commonLikes = friend.getLikes();
				firstFriend = false;
			} else {
				commonLikes.retainAll(friend.getLikes());
			}
		}
		return commonLikes;
	}

}
